package br.cnpq.webflowah.adaptation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

import br.cnpq.webflowah.beans.Operacao;
import br.cnpq.webflowah.beans.PlanQuality;
import br.cnpq.webflowah.beans.PlanoExecucao;
import br.ufrn.opencopi.exceptions.AdaptationIsNotNecessaryException;
import br.ufrn.opencopi.exceptions.AdaptationIsNotPossibleException;
import br.ufrn.opencopi.exceptions.VertexNotFoundException;
import br.ufrn.opencopi.graph.EdgeTuple;
import br.ufrn.opencopi.graph.FinalVertex;
import br.ufrn.opencopi.graph.Graph;
import br.ufrn.opencopi.graph.GraphController;
import br.ufrn.opencopi.graph.GraphPath;
import br.ufrn.opencopi.graph.PathAdaptationCost;
import br.ufrn.opencopi.graph.ServiceVertex;

public class AdaptationManager {

	private static final Logger log = Logger.getLogger(AdaptationManager.class);
	/**
	 * This operation is responsible for prepare the adatataion. First it calls
	 * the operation selectNewExecutionPlan(...) and after do the new process of
	 * execution plan change.
	 * 
	 * @param inFaulty
	 *            --> in case of a service faulty the process is a little bit
	 *            different
	 * @param actualPlan
	 * @param planos
	 * @param adaptationPriority
	 * @param graph
	 * @param planQualityList
	 *            --> armazena a qualidade de SERVICO dos planos para nao
	 *            precisar recalcular
	 * @throws AdaptationIsNotPossibleException
	 * @throws AdaptationIsNotPossibleException
	 */
	public static List<Operacao> prepareAdaptation(boolean inFaulty,
			Graph graph, GraphPath actualPath, int adaptationPriority,
			String actualServiceURI, List<PlanQuality> planQualityList)
			throws AdaptationIsNotNecessaryException,
			AdaptationIsNotPossibleException {

		List<Operacao> choisedPlan = null;
		List<Operacao> actualPlan = actualPath.getPlan();
		List<Operacao> newPlan = null;
		PlanAdaptationPriority planPriority = null;
		try {
			planPriority = selectNewExecutionPlan(inFaulty, graph, actualPath,
					actualPlan, adaptationPriority, actualServiceURI,
					planQualityList);
			choisedPlan = planPriority.getGraphPath().getPlan();
		} catch (AdaptationIsNotPossibleException e) {
			// TODO: handle exception
			if (inFaulty) {
				// STOP EXECUTION
				throw e;
			} else {
				// Nothing to do.
				throw new AdaptationIsNotNecessaryException(
						"It was not possible find a new execution plan and the application will continue running the actual option!");
			}
		}

		if (inFaulty)
			if (choisedPlan != null && planPriority != null)
				newPlan = composeNewProcess(inFaulty, actualPlan,
						choisedPlan, graph, planPriority);
			else {
				if (!choisedPlan.equals(actualPlan))// isso eh, se sao
													// diferentes: call
													// adaptation
					if (choisedPlan == null)
						throw new AdaptationIsNotNecessaryException(
								"The choisedPlan is null but the application can continue running!");
					else
						newPlan = composeNewProcess(inFaulty, actualPlan,
								choisedPlan, graph, planPriority);
				else
					throw new AdaptationIsNotNecessaryException(
							"The best option is the actual plan. The application will continue running this option!");
			}
		return newPlan;
	}

	private static List<Operacao> composeNewProcess(boolean inFaulty,
			List<Operacao> actualPlan, List<Operacao> choisedPlan, Graph graph,
			PlanAdaptationPriority planPriority) {
		
		// perform adaptation
		// TODO
		// System.out.println("Adaptation beginning:");
		// System.out.println("planPriority: "+planPriority.toString());
		// System.out.println("---");
		// System.out.println("Selected plan:");
		//Operacao op;
		/*
		 * for(int i = 0; i < choisedPlan.size(); i++ ){ op =
		 * choisedPlan.get(i); System.out.println("Service="+op.getURI()); }
		 */

		GraphPath newGraphPathPlan = planPriority.getGraphPath();
		ServiceVertex cp = (ServiceVertex) planPriority.getCoincidencePoint();
		EdgeTuple tupleCP = newGraphPathPlan.getTupleByEdgeOut(cp);

		//PlanoExecucao newExecutionPlan = new PlanoExecucao();
		ArrayList<Operacao> list = new ArrayList<Operacao>();
		// Varrendo tupla at� encontrar FinalVertex como vertex in
		while (!(tupleCP.getVertexIn() instanceof FinalVertex)) {
			list.add(((ServiceVertex) tupleCP.getVertexIn()).getOperacao());
			// pegando a proxima tupla, ie, a tupla cujo edgeIn eh = a edgeOut
			// da tupla atual
			tupleCP = newGraphPathPlan.getTupleByEdgeOut(tupleCP.getVertexIn());

		}
		//newExecutionPlan.setOperacoes(list);

		return list;
	}

	/**
	 * This operation choises the new execution plan to substitute the actual
	 * execution plan
	 * 
	 * @param actualPlan
	 * @param planos
	 * @param adaptationPriority
	 * @param graph
	 * @param planQualityList
	 *            --> qualidade de SERVICO de cada plano (nao precisa
	 *            recalcular)
	 * @return
	 * @throws AdaptationIsNotPossibleException
	 */
	private static PlanAdaptationPriority selectNewExecutionPlan(
			boolean inFaulty, Graph graph, GraphPath actualPath,
			List<Operacao> actualPlan, int adaptationPriority,
			String actualServiceURI, List<PlanQuality> planQualityList)
			throws AdaptationIsNotPossibleException {
		List<PlanAdaptationPriority> priorityList = new ArrayList<PlanAdaptationPriority>();
		List<PlanAdaptationPriority> orderedPriorityList = new ArrayList<PlanAdaptationPriority>();
		List<Operacao> choisedPlan = null;
		// List<List<Operacao>> planos = graph.getWorkflow().getPlanos();
		PlanAdaptationPriority planPriority;
		double serviceQualityMax = 0;
		double adaptationQualityMax = 0;

		// for each execution plan, calculate its absolute qualities
		// for(int i=0; i<planos.size(); i++){
		for (int i = 0; i < graph.getPaths().size(); i++) {
			// planPriority = calculateAbsoluteQualities(planos.get(i),graph,
			// actualPath, actualServiceURI);
			if (!(actualPath.equals(graph.getPaths().get(i)))) {
				planPriority = calculateAbsoluteQualities(
						graph.getPaths().get(i).getPlan(), graph, actualPath,
						graph.getPaths().get(i), actualServiceURI,
						planQualityList);
				if (planPriority.getAbsoluteServiceQuality() > serviceQualityMax)
					serviceQualityMax = planPriority
							.getAbsoluteServiceQuality();
				if (planPriority.getAbsoluteAdaptationQuality() > adaptationQualityMax)
					adaptationQualityMax = planPriority
							.getAbsoluteAdaptationQuality();
				priorityList.add(planPriority);
			}
		}
		// For each execution plan, calculate its relative quality (plan
		// priority)
		for (int i = 0; i < priorityList.size(); i++) {
			orderedPriorityList
					.add(calculatePlanPriority(priorityList.get(i),
							serviceQualityMax, adaptationQualityMax,
							adaptationPriority));
		}
		// ordenate priorityList
		Collections.sort(orderedPriorityList,
				new Comparator<PlanAdaptationPriority>() {
					public int compare(PlanAdaptationPriority o1,
							PlanAdaptationPriority o2) {
						PlanAdaptationPriority p1 = (PlanAdaptationPriority) o1;
						PlanAdaptationPriority p2 = (PlanAdaptationPriority) o2;
						return p1.getPriority() < p2.getPriority() ? +1 : (p1
								.getPriority() > p2.getPriority() ? -1 : 0);
					}
				});

		// select the best substitute plan
		try {
			// the first plan is the best one. But if is in faulty and
			// choisedPlan = actualPlan, get the second better plan
			// choisedPlan =
			// orderedPriorityList.get(0).getGraphPath().getPlan();
			// planPriority = orderedPriorityList.get(0);
			// if (choisedPlan.equals(actualPlan) && inFaulty){
			// choisedPlan =
			// orderedPriorityList.get(1).getGraphPath().getPlan();
			// planPriority = orderedPriorityList.get(1);
			// }
			Iterator<PlanAdaptationPriority> i = orderedPriorityList.iterator();
			while (i.hasNext()) {
				PlanAdaptationPriority planAdp = i.next();
				Iterator<Operacao> op = planAdp.getGraphPath().getPlan()
						.iterator();
				boolean flag = true;
				log.debug("Servico em falha: " + actualServiceURI);
				while (op.hasNext()) {
					Operacao operacao = op.next();
					log.debug("Comparar com: " + operacao.getURI());
					if (operacao.getURI().toASCIIString().equals(actualServiceURI)) {
						flag = false;
						break;
					}
				}
				if (flag)
					return planAdp;
			}

		} catch (IndexOutOfBoundsException ex) {
			throw new AdaptationIsNotPossibleException(
					"There are not possible plans to substitute the faulty plan");
		}
		log.info("Não tem plano");
		return null;
	}

	/**
	 * This operation calculates de relative priority of a plan
	 * 
	 * @param plan
	 * @param adaptationPriority
	 * @param graph
	 * @return
	 */
	private static PlanAdaptationPriority calculatePlanPriority(
			PlanAdaptationPriority adaptationPriority,
			double serviceQualityMax, double serviceAdaptationMax, int priority) {

		double p = 0;

		// calculating the relative values
		double relativeServiceQuality = adaptationPriority
				.getAbsoluteServiceQuality() / serviceQualityMax; // entre zero
																	// e um,
																	// priorizando
																	// a maior
																	// qualidade
		double relativeAdaptationQuality = 1 - (adaptationPriority
				.getAbsoluteAdaptationQuality() / serviceAdaptationMax); // entre
																			// zero
																			// e
																			// um,
																			// priorizando
																			// o
																			// menor
																			// custo

		// calculating the final priority with relative quality values and the
		// prioryty of adaptation (service quality or adaptation quality
		// priority)
		switch (priority) {
		case AdaptationConstants.FULL_SERVICE_QUALITY:
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_FULL_SERVICE_QUALITY)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_FULL_SERVICE_QUALITY);
			break;
		case AdaptationConstants.SERVICE_QUALITY:
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_SERVICE_QUALITY)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_SERVICE_QUALITY);
			break;
		case AdaptationConstants.BALANCED:
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_BALANCED)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_BALANCED);
			break;
		case AdaptationConstants.EFFICIENT_ADAPTATION:
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_EFFICIENT_ADAPTATION)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_EFFICIENT_ADAPTATION);
			break;
		case AdaptationConstants.FULL_EFFICIENT_ADAPTATION:
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_FULL_EFFICIENT_ADAPTATION)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_FULL_EFFICIENT_ADAPTATION);
			break;
		default: // BALANCED
			p = (relativeServiceQuality * AdaptationConstants.ExPQ_BALANCED)
					+ (relativeAdaptationQuality * AdaptationConstants.EfA_BALANCED);
		}

		adaptationPriority.setPriority(p);

		return adaptationPriority;
	}

	/**
	 * Calculates the absolute qualities of each plan
	 * 
	 * @param plan
	 * @param graph
	 * @return
	 */
	private static PlanAdaptationPriority calculateAbsoluteQualities(
			List<Operacao> plan, Graph graph, GraphPath actualPath,
			GraphPath newPath, String actualServiceURI,
			List<PlanQuality> planQualityList) {
		PlanAdaptationPriority adapQuality = new PlanAdaptationPriority();
		// setting absoluteServiceQuality
		adapQuality.setAbsoluteServiceQuality(calculateAbsoluteServiceQuality(
				plan, planQualityList));

		// setting absoluteAdaptationQuality and coincidencePoint vertex
		PathAdaptationCost pathAdapCost = calculteAbsoluteAdaptationQuality(
				graph, actualPath, newPath, actualServiceURI);
		adapQuality.setAbsoluteAdaptationQuality(pathAdapCost
				.getAdaptationCost());
		adapQuality.setCoincidencePoint(pathAdapCost.getCoincidencePoint());

		// setting GraphPath in PlanAdaptationPriority
		adapQuality.setGraphPath(pathAdapCost.getPath());
		return adapQuality;
	}

	/**
	 * 
	 * Veja que estamos reaproveitando a qualidade calculada na fase de sele��o.
	 * N�o necessariamente precisar�amos entrar em tantos m�todos para pegar
	 * esse valor por�m para manter a organiza��o do c�digo, preferimos descer
	 * at� esse n�vel ao inv�s de pegar o valor diretamente em
	 * selectNewExecutionPlan(..). SeletorPlanoExecucao.java foi adaptado.
	 * 
	 * @param plan
	 * @return
	 */
	private static double calculateAbsoluteServiceQuality(List<Operacao> plan,
			List<PlanQuality> planQualityList) {
		/*
		 * Operacao op; int sameImpact = 0;
		 * 
		 * Iterator<Operacao> operacoes = plan.iterator();
		 * while(operacoes.hasNext()){ op = operacoes.next();
		 * 
		 * //consultar no banco de qoc,qos qual o impacto do serviço. O impacto
		 * do servico eh //um calculo de acordo com cada parametro do mesmo. QoC
		 * qoc = null; QoS qos = null;
		 * 
		 * //impacto desejado pelo cliente String impactoDesejado =
		 * op.getObjetivo().getImpactoDesejado();
		 * 
		 * //recuperando metadados do banco de dados Metadata metadata =
		 * QualityParameterDB.listParametersByService(op.getURI().toString());
		 * 
		 * //calculando para QoS Iterator<QoS> qosList = ((ArrayList<QoS>)
		 * metadata.getQosList()).iterator(); while(qosList.hasNext()){ qos =
		 * qosList.next(); //calcular para parametro específico //primeiro:
		 * verificando se o impacto é = ao impacto desejado
		 * if(qos.getImpact().equals(impactoDesejado)) sameImpact++; }
		 * 
		 * //calculando para QoC Iterator<QoC> qocList = ((ArrayList<QoC>)
		 * metadata.getQocList()).iterator(); while(qosList.hasNext()){ qoc =
		 * qocList.next(); //calcular para parametro específico //primeiro:
		 * verificando se o impacto é = ao impacto desejado
		 * if(qoc.getImpact().equals(impactoDesejado)) sameImpact++; } } return
		 * sameImpact;
		 */
		PlanQuality pQuality;
		for (int i = 0; i < planQualityList.size(); i++) {
			pQuality = planQualityList.get(i);
			if (pQuality.getPlano().equals(plan)) {
				return pQuality.getQuality();
			}
		}

		return (Double) null;

	}

	private static PathAdaptationCost calculteAbsoluteAdaptationQuality(
			Graph graph, GraphPath actualPath, GraphPath newPath,
			String actualServiceURI) {
		PathAdaptationCost pathAdapCost = null;
		try {
			pathAdapCost = GraphController.calculatePathsAdaptationCost(graph,
					actualPath, newPath, actualServiceURI);
		} catch (VertexNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return pathAdapCost;
	}

}
